/*
 * Copyright (c) 2021-2023, Wei.Studio
 *
 * License: Apache-2.0
 *
 * Change Logs:
 * Date           Author        Notes
 * 2021-07-13     Wei.Studio    the first version
 */



#include "autocheck.h"
#include "autoconfig.h"
#include "qe_assert.h"
#include "qe_memory.h"
#include "qe_string.h"



#if defined(CONFIG_NANO)
typedef struct
{
	qe_size total_bytes;
	qe_size alloced_bytes;
	void *pool;
	qe_size pool_size;
	qemem_allocator *allocator;
} qemem_context;

static qe_size mm_get_pool_size(void);

void qe_weak qemem_lock_acquire()
{
}

void qe_weak qemem_lock_release()
{
}

#if defined(CONFIG_MEM_BGET)
#include "bget.h"
static void *bgets_malloc(qe_size size)
{
	return bget(size);
}

static void *bgets_calloc(qe_size nitems, qe_size size)
{
	if (!nitems || !size)
		return QE_NULL;
	qe_size alloc = nitems * size;
	void *m = bget(alloc);
	qe_assert(m != QE_NULL);
	qe_memset(m, 0x0, alloc);
	return m;
}

static void *bgets_realloc(void *mem, qe_size size)
{
	if (!mem || !size)
		return QE_NULL;

	void *m = bgetr(mem, size);
	qe_assert(m != QE_NULL);
	return m;
}

static void bgets_free(void *mem)
{
	brel(mem);
}

static void bgets_pool(void *pool, qe_size size)
{
	bpool(pool, size);
}

static void bgets_minfo(qemem_minfo *info)
{
	long alloc, totfree, maxfree, nget, nrel;
	bstats(&alloc, &totfree, &maxfree, &nget, &nrel);
	info->total = alloc + totfree;
	info->used  = alloc;
	info->free  = totfree;
}

static qemem_allocator bgets_allocator = {
	.malloc   = bgets_malloc,
	.calloc   = bgets_calloc,
	.realloc  = bgets_realloc,
	.free     = bgets_free,
	.pool     = bgets_pool,
	.minfo    = bgets_minfo,
	.flags    = QEMEM_POOL,
};
#endif

#if defined(CONFIG_MEM_TLSF)
#include "tlsf.h"
static tlsf_t tlsf;
static void *__tlsf_malloc(qe_size size)
{	
	return tlsf_malloc(tlsf, size);
}

static void *__tlsf_calloc(qe_size nitems, qe_size size)
{
	if (!nitems || !size)
		return QE_NULL;
	qe_size alloc = nitems * size;
	return __tlsf_malloc(alloc);
}

static void *__tlsf_realloc(void *mem, qe_size size)
{	
	return tlsf_realloc(tlsf, mem, size);
}

static void *__tlsf_memalign(qe_size align, qe_size size)
{
	return tlsf_memalign(tlsf, align, size);
}

static void __tlsf_free(void *mem)
{
	tlsf_free(tlsf, mem);
}

static void __tlsf_pool(void *pool, qe_size size)
{
	tlsf = tlsf_create_with_pool(pool, size);
}

typedef struct {
    qe_size total_free;
    qe_size largest_free;
    qe_size block_size_min;
    qe_size block_size_max;
    qe_size current_used;
    qe_double usage;
    qe_double fragmentation_rate;
} tlsf_memory_stats;

void tlsf_walker_callback(void* ptr, size_t size, int used, void* user) 
{
    tlsf_memory_stats* stats = (tlsf_memory_stats*)user;
    
    if (!used) {
        qe_size effective_size = size - tlsf_alloc_overhead();
        stats->total_free += effective_size;
        
        if (effective_size > stats->largest_free) {
            stats->largest_free = effective_size;
        }
    } else {
        stats->current_used += size - tlsf_alloc_overhead();
        if (stats->block_size_max < size) {
            stats->block_size_max = size;
        }
    }
}

static tlsf_memory_stats get_memory_stat(void)
{
    tlsf_memory_stats stats = {0};
    pool_t pool = tlsf_get_pool(tlsf);
    tlsf_walk_pool(pool, tlsf_walker_callback, &stats);
    stats.block_size_min = tlsf_block_size_min();
    stats.usage = (float)stats.current_used / (float)(stats.total_free + stats.current_used) * 100.0f;
    if (stats.total_free == 0) {
        stats.fragmentation_rate = 0.0f;
    } else {
        stats.fragmentation_rate = (1.0f - (float)stats.largest_free / stats.total_free) * 100.0f;
    }
    return stats;
}

static void __tlsf_show_meminfo(void)
{
	tlsf_memory_stats stats = get_memory_stat();
	qe_printf("total size    : %u bytes %s\r\n", mm_get_pool_size(), qe_byte_to_str(mm_get_pool_size()));
	qe_printf("free          : %u bytes %s\r\n", stats.total_free, qe_byte_to_str(stats.total_free));
	qe_printf("block min     : %u bytes\r\n", stats.block_size_min);
	qe_printf("block max     : %u bytes\r\n", stats.block_size_max);
    qe_printf("usage         : %u bytes %s %.2f%\r\n",
        stats.current_used, 
        qe_byte_to_str(stats.current_used),
        stats.usage);
	qe_printf("fragmentation : %.2f%\r\n", stats.fragmentation_rate);
}

static qemem_allocator tlsf_allocator = {
	.malloc = __tlsf_malloc,
	.calloc = __tlsf_calloc,
	.realloc = __tlsf_realloc,
	.memalign = __tlsf_memalign,
	.free = __tlsf_free,
	.pool = __tlsf_pool,
	.show_meminfo = __tlsf_show_meminfo,
	.flags   = QEMEM_POOL
};
#endif

static qemem_context mm = {
	.alloced_bytes = 0,
	.total_bytes   = 0,
	.pool          = QE_NULL,
	.pool_size     = 0,
#if defined(CONFIG_MEM_BGET)
	.allocator     = &bgets_allocator,
#elif defined(CONFIG_MEM_TLSF)
	.allocator     = &tlsf_allocator,
#else
	.allocator     = QE_NULL,
#endif
};

static qe_size mm_get_pool_size(void)
{
	return mm.pool_size;
}

void qemem_set_pool(void *start, qe_size size)
{
	qe_assert(mm.allocator != QE_NULL);

	if (!(mm.allocator->flags & QEMEM_POOL))
		return;

	qe_assert(mm.allocator->pool != QE_NULL);

	mm.allocator->pool(start, size);
	mm.pool = start;
	mm.pool_size = size;
}

void qemem_get_minfo(qemem_minfo *info)
{
	qe_assert(mm.allocator != QE_NULL);
	qe_assert(mm.allocator->minfo != QE_NULL);
	
	qemem_lock_acquire();
	mm.allocator->minfo(info);
	qemem_lock_release();
}

void qemem_show_meminfo(void)
{
	qe_assert(mm.allocator != QE_NULL);
	qe_assert(mm.allocator->show_meminfo != QE_NULL);

	qemem_lock_acquire();
	mm.allocator->show_meminfo();
	qemem_lock_release();
}

void qemem_set_allocator(qemem_allocator *allocator)
{
	mm.allocator = allocator;
}

void *qe_malloc(qe_size size)
{
	void *mem;
	qe_assert(mm.allocator != QE_NULL);
	qe_assert(mm.allocator->malloc != QE_NULL);
	if ((mm.allocator->flags & QEMEM_POOL) && !mm.pool) {
		return QE_NULL;
	}
	
	qemem_lock_acquire();
	mem = mm.allocator->malloc(size);
	qemem_lock_release();
	return mem;
}

void *qe_memalign(qe_size align, qe_size size)
{
	void *mem;
	qe_assert(mm.allocator != QE_NULL);
	qe_assert(mm.allocator->memalign != QE_NULL);
	if ((mm.allocator->flags & QEMEM_POOL) && !mm.pool) {
		return QE_NULL;
	}

	qemem_lock_acquire();
	mem = mm.allocator->memalign(align, size);
	qemem_lock_release();
	return mem;
}


void *qe_calloc(qe_size num, qe_size size)
{
	void *mem;

	qe_assert(mm.allocator != QE_NULL);
	qe_assert(mm.allocator->calloc != QE_NULL);

	if ((mm.allocator->flags & QEMEM_POOL) && !mm.pool) {
		return QE_NULL;
	}

	qemem_lock_acquire();
	mem = mm.allocator->calloc(num, size);
	qemem_lock_release();
	return mem;
}

void *qe_realloc(void *buf, qe_size size)
{
	void *mem;

	qe_assert(mm.allocator != QE_NULL);
	qe_assert(mm.allocator->realloc != QE_NULL);

	if ((mm.allocator->flags & QEMEM_POOL) && !mm.pool) {
		return QE_NULL;
	}

	qemem_lock_acquire();
	mem = mm.allocator->realloc(buf, size);
	qemem_lock_release();
	return mem;
}

void qe_free(void *ptr)
{
	qe_assert(mm.allocator != QE_NULL);
	qe_assert(mm.allocator->free != QE_NULL);

	if ((mm.allocator->flags & QEMEM_POOL) && !mm.pool) {
		return;
	}

	qemem_lock_acquire();
	mm.allocator->free(ptr);
	qemem_lock_release();
}

/**
 * @brief This function will copy memory content from source address to destination
 * address.
 *
 * @param[in] dst   : the address of destination memory
 * @param[in] src   : the address of source memory
 * @param[in] count : the copied length
 *
 * @return the address of destination memory
 */
void *qe_memcpy(void *dst, const void *src, qe_size count)
{
#if (CONFIG_MEMCPY_TINY == 1)
	char *tmp = (char *)dst, *s = (char *)src;
	qe_ubase len;
	if (tmp <= s || tmp > (s + count)) {
		while (count--)
			*tmp ++ = *s ++;
	} else {
		for (len = count; len > 0; len --)
			tmp[len - 1] = s[len - 1];
	}
	return dst;
#else
#define __UNALIGNED(X, Y) \
    (((qe_base)X & (sizeof (qe_base) - 1)) | ((qe_base)Y & (sizeof (qe_base) - 1)))
#define BIGBLOCKSIZE    (sizeof (long) << 2)
#define LITTLEBLOCKSIZE (sizeof (long))
#define TOO_SMALL(LEN)  ((LEN) < BIGBLOCKSIZE)
	char *dst_ptr = (char *)dst;
    char *src_ptr = (char *)src;
    long *aligned_dst;
    long *aligned_src;
    int len = count;

	/* If the size is small, or either SRC or DST is unaligned,
	then punt into the byte copy loop.	This should be rare. */
	if (!TOO_SMALL((unsigned int)len) && !__UNALIGNED(src_ptr, dst_ptr)) 
	{
		aligned_dst = (long *)dst_ptr;
		aligned_src = (long *)src_ptr;
		
		/* Copy 4X long words at a time if possible. */
		while ((unsigned int)len >= (unsigned int)BIGBLOCKSIZE)
		{
            *aligned_dst++ = *aligned_src++;
            *aligned_dst++ = *aligned_src++;
            *aligned_dst++ = *aligned_src++;
            *aligned_dst++ = *aligned_src++;
            len -= BIGBLOCKSIZE;
		}

		/* Copy one long word at a time if possible. */
        while ((unsigned int)len >= LITTLEBLOCKSIZE)
        {
            *aligned_dst++ = *aligned_src++;
            len -= LITTLEBLOCKSIZE;
        }

		/* Pick up any residual with a byte copier. */
		dst_ptr = (char *)aligned_dst;
		src_ptr = (char *)aligned_src;
	}

	while (len--)
		*dst_ptr++ = *src_ptr++;

	return dst;
#undef __UNALIGNED
#undef BIGBLOCKSIZE
#undef LITTLEBLOCKSIZE
#undef TOO_SMALL
#endif
}

/**
 * @brief Fill a region of memory with the given value
 * @s: Pointer to the start of the area.
 * @c: The byte to fill the area with
 * @count: The size of the area.
 *
 */
void *qe_memset(void *s, int c, qe_size count)
{
#if (CONFIG_MEMSET_TINY == 1)
	char *xs = (char *)s;
	while (count--)
		*xs++ = c;
	return s;
#else
#define __LBLOCKSIZE		(sizeof(long))
#define __UNALIGNED(x)		((qe_base)x & (__LBLOCKSIZE-1))
#define __TOO_SMALL(len)  	((len) < __LBLOCKSIZE)

	unsigned int i;
	char *m = (char *)s;
	unsigned long buffer;
	unsigned long *aligned_addr;
	unsigned int d = c & 0xff;	/* To avoid sign extension, copy C to an unsigned variable */

	if (!__TOO_SMALL(count) && !__UNALIGNED(s)) {

		/* If we get this far, we know that count is large and s is word-aligned. */
		aligned_addr = (unsigned long *)s;

		/* Store d into each char sized location in buffer so that
         * we can set large blocks quickly.
         */
		if (__LBLOCKSIZE == 4) {
			buffer = (d << 8) | d;
			buffer |= (buffer << 16);
		} else {
			buffer = 0;
			for (i = 0; i < __LBLOCKSIZE; i ++)
				buffer = (buffer << 8) | d;
		}

		while (count >= __LBLOCKSIZE * 4) {
			*aligned_addr++ = buffer;
			*aligned_addr++ = buffer;
			*aligned_addr++ = buffer;
			*aligned_addr++ = buffer;
			count -= 4 * __LBLOCKSIZE;
		}

		while (count >= __LBLOCKSIZE) {
            *aligned_addr++ = buffer;
            count -= __LBLOCKSIZE;
        }

		/* Pick up the remainder with a bytewise loop. */
		m = (char *)aligned_addr;
	}

	while (count--)
		*m++ = (char)d;
	return s;
#undef __LBLOCKSIZE
#undef __UNALIGNED
#undef __TOO_SMALL
#endif
}

/**
 * @brief Compare two areas of memory
 * @cs: One area of memory
 * @ct: Another area of memory
 * @count: The size of the area.
 */
int qe_memcmp(const void *cs,const void *ct, qe_size count)
{
	const unsigned char *su1, *su2;
	int res = 0;

	for( su1 = cs, su2 = ct; 0 < count; ++su1, ++su2, count--)
		if ((res = *su1 - *su2) != 0)
			break;
	return res;
}

/**
 * @brief Copy one area of memory to another
 * @dest: Where to copy to
 * @src: Where to copy from
 * @count: The size of the area.
 */
void *qe_memmove(void *dest, const void *src, qe_size count)
{
	char *tmp, *s;

	if (dest <= src) {
		qe_memcpy(dest, (void *)src, count);
	} else {
		tmp = (char *) dest + count;
		s = (char *) src + count;
		while (count--)
			*--tmp = *--s;
	}

	return dest;
}

/**
 * @brief Find a character in an area of memory.
 * @addr: The memory area
 * @c: The byte to search for
 * @size: The size of the area.
 *
 * returns the address of the first occurrence of @c, or 1 byte past
 * the area if @c is not found
 */
void *qe_memscan(void *addr, int c, qe_size size)
{
	unsigned char * p = (unsigned char *) addr;

	while (size) {
		if (*p == c)
			return (void *) p;
		p++;
		size--;
	}
	return (void *) p;
}
#endif


void *qe_memdup(void *mem, qe_size size)
{
	if (!mem || !size)
		return QE_NULL;

	void *out = qe_malloc(size);

	if (!out)
		return QE_NULL;

	qe_memcpy(out, mem, size);

	return out;
}
